package com.ikingtech.platform.service.office.service;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.json.JSONArray;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.deepoove.poi.XWPFTemplate;
import com.deepoove.poi.config.Configure;
import com.deepoove.poi.config.ConfigureBuilder;
import com.deepoove.poi.data.Numberings;
import com.deepoove.poi.data.TextRenderData;
import com.deepoove.poi.policy.HackLoopTableRenderPolicy;
import com.deepoove.poi.policy.TextRenderPolicy;
import com.ikingtech.framework.sdk.context.exception.FrameworkException;
import com.ikingtech.framework.sdk.core.response.R;
import com.ikingtech.framework.sdk.office.api.AffixFileApi;
import com.ikingtech.framework.sdk.office.api.OfficeDataApi;
import com.ikingtech.framework.sdk.office.api.OfficeDataCustomApi;
import com.ikingtech.framework.sdk.office.model.dto.AffixFileDto;
import com.ikingtech.framework.sdk.office.model.dto.OfficeMakeDto;
import com.ikingtech.framework.sdk.office.model.dto.OfficeTempDto;
import com.ikingtech.framework.sdk.office.model.dto.OfficeTempFileDto;
import com.ikingtech.framework.sdk.office.model.enums.FileTypeEnum;
import com.ikingtech.framework.sdk.office.model.enums.OfficeDataEnums;
import com.ikingtech.framework.sdk.office.model.query.OfficeDataManagementQuery;
import com.ikingtech.framework.sdk.office.model.vo.*;
import com.ikingtech.framework.sdk.office.utils.CustomFileUtil;
import com.ikingtech.framework.sdk.office.utils.ExcelTemplateUtil;
import com.ikingtech.framework.sdk.office.utils.OfficeFileUtils;
import com.ikingtech.framework.sdk.oss.api.OssApi;
import com.ikingtech.framework.sdk.oss.model.OssFileDTO;
import com.ikingtech.framework.sdk.utils.Tools;
import com.ikingtech.platform.service.office.entity.OfficeTemp;
import com.ikingtech.platform.service.office.entity.OfficeTempRecord;
import com.ikingtech.platform.service.office.mapper.OfficeTempMapper;
import com.ikingtech.platform.service.office.mapper.OfficeTempRecordMapper;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.transaction.annotation.Transactional;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * <p>
 * office-模板 服务实现类
 * </p>
 *
 * @author lqb
 */
@Slf4j
@RequiredArgsConstructor
public class OfficeTempService extends ServiceImpl<OfficeTempMapper, OfficeTemp> {

    private final OssApi ossApi;
    private final AffixFileApi affixFileApi;
    private final OfficeDataApi officeDataApi;
    private final OfficeDataCustomApi officeDataCustomApi;
    private final OfficeTempRecordMapper officeTempRecordMapper;


    /**
     * 添加
     *
     */
    @Transactional(rollbackFor = Exception.class)
    public R<String> add(OfficeTempDto dto) {
        // 新增模板
        OfficeTemp temp = dto.copy(new OfficeTemp())
                .setDelFlag(Boolean.FALSE);
        temp.setId(Tools.Id.uuid());
        // 创建office模板记录
        String recordId = this.createOfficeTempRecord(temp, dto.getAffixFileId(), null);
        // insert 模板
        temp.setOfficeTempRecordId(recordId);
        temp.insert();
        return R.ok(temp.getId());
    }

    /**
     * 创建office模板记录
     *
     */
    private String createOfficeTempRecord(OfficeTemp temp, String affixFileId, String url) {
        // 不启用
        if (StringUtils.isNoneBlank(temp.getOfficeTempRecordId())) {
            OfficeTempRecord old = new OfficeTempRecord()
                    .setEnable(Boolean.FALSE);
            old.setId(temp.getOfficeTempRecordId());
            old.updateById();
        }
        // 模板历史记录
        OfficeTempRecord record = new OfficeTempRecord()
                .setOfficeTempId(temp.getId())
                .setEnable(Boolean.TRUE)
                .setDelFlag(Boolean.FALSE)
                .setFileName(temp.getTempName());
        record.setId(Tools.Id.uuid());
        // 附件不存在 添加默认附件
        if (StringUtils.isBlank(affixFileId)) {
            byte[] bytes = new byte[0];
            if (StringUtils.isBlank(url)) {
                if (FileTypeEnum.WORD.equals(temp.getFileType())) {
                    bytes = OfficeFileUtils.createWordFileByte();
                } else if (FileTypeEnum.EXCEL.equals(temp.getFileType())) {
                    bytes = OfficeFileUtils.createExcelFileByte();
                }
            } else {
                bytes = CustomFileUtil.readUrlFileToByte(url);
            }
            // 附件
            OssFileDTO wordTempOss = null;
//            OssFileDTO wordTempOss = ossRpcApi.uploadByte(temp.getTempName() + temp.getFileType().getSuffix(), bytes).getData();
            if (ObjectUtils.isEmpty(wordTempOss)) {
                throw new FrameworkException("创建模板文件异常");
            }
            AffixFileDto affixFile = BeanUtil.copyProperties(wordTempOss, AffixFileDto.class);
            affixFile.setOssFileId(wordTempOss.getId())
                    .setBusinessId(record.getId());
            affixFileId = affixFileApi.add(affixFile).getData();
        }
        record.setAffixFileId(affixFileId);
        record.insert();
        return record.getId();
    }

    /**
     * only office 回调
     *
     */
    @Transactional(rollbackFor = Exception.class)
    public void onlyOfficeCallBack(String id, OfficeTempFileDto dto) {
        OfficeTemp temp = this.getById(id);
        if (ObjectUtils.isEmpty(temp)) {
            throw new FrameworkException("office模板不存在");
        }
        // 创建office模板记录
        String recordId = this.createOfficeTempRecord(temp, null, dto.getUrl());

        // 修改模板记录
        OfficeTemp change = new OfficeTemp()
                .setOfficeTempRecordId(recordId);
        change.setId(temp.getId());
        change.updateById();
    }

    public R<OfficeTempVo> detail(OfficeTemp temp) {
        // 获取模板对应的记录信息
        OfficeTempRecord record = this.officeTempRecordMapper.selectById(temp.getOfficeTempRecordId());
        if (ObjectUtils.isEmpty(record)) {
            throw new FrameworkException("模板记录不存在");
        }
        AffixFileVo file = affixFileApi.detail(record.getAffixFileId()).getData();
        if (ObjectUtils.isEmpty(file)) {
            throw new FrameworkException("模板文件不存在");
        }
        return R.ok(temp.copy(new OfficeTempVo())
                .setFileName(record.getFileName())
                .setAffixFileId(file.getId())
                .setOssFileId(file.getOssFileId()));
    }

    /**
     * 生成office文件
     *
     * @param temp   临时雇员
     * @param params params
     * @return 执行结果 返回生成的文件id
     */
    @SneakyThrows
    @Transactional(rollbackFor = Exception.class)
    public R<String> make(OfficeTemp temp, OfficeMakeDto params) {
        // 下载模板
        InputStream tempInputStream = this.downloadOfficeTemp(temp);
        byte[] bytes = new byte[0];
        List<OfficeDataAllVo> dataList = this.encapsulationData(params);
        if (FileTypeEnum.WORD.equals(temp.getFileType())) {
            // 封装数据
            List<WordTempDataVo> wordDataList = new ArrayList<>();
            this.parsingWordData(wordDataList, dataList);
            bytes = this.makeWord(tempInputStream, wordDataList);
        } else if (FileTypeEnum.EXCEL.equals(temp.getFileType())) {
            // 封装数据
            ExcelTempDataVo data = new ExcelTempDataVo();
            this.parsingExcelData(data, dataList);
            bytes = ExcelTemplateUtil.makeExcelByFill(tempInputStream, data.getListData(), data.getTotalData());
        }
        R<OssFileDTO> fileR = null;
//        R<OssFileDTO> fileR =    ossRpcApi.uploadByte(
//                temp.getTempName() + System.currentTimeMillis() + temp.getFileType().getSuffix(),
//                bytes);
        if (fileR.isSuccess()) {
            return R.ok(fileR.getData().getId());
        }
        return R.failed("上次生成文件失败");
    }


    /**
     * 下载office模板
     *
     * @return 执行结果
     */
    private InputStream downloadOfficeTemp(OfficeTemp temp) {
        OfficeTempRecord record = this.officeTempRecordMapper.selectById(temp.getOfficeTempRecordId());
        if (ObjectUtils.isEmpty(record)) {
            throw new FrameworkException("office模板记录不存在");
        }
        AffixFileVo tempFile = affixFileApi.detail(record.getAffixFileId()).getData();
        if (ObjectUtils.isEmpty(temp)) {
            throw new FrameworkException("office模板文件不存在");
        }
        byte[] tempBytes = ossApi.downloadByte(tempFile.getOssFileId()).getData();
        if (ObjectUtils.isEmpty(tempBytes)) {
            throw new FrameworkException("office模板文件下载读取失败");
        }
        return new ByteArrayInputStream(tempBytes);
    }

    /**
     * 生成word
     *
     * @param tempInputStream 模板
     * @param dataList        数据列表
     * @return 执行结果
     */
    private byte[] makeWord(InputStream tempInputStream, List<WordTempDataVo> dataList) throws IOException {
        // loading数据
        ConfigureBuilder configureBuilder = Configure.builder();
        Map<String, Object> data = new HashMap<>(dataList.size());
        this.loadingWordData(dataList.stream().collect(Collectors.groupingBy(WordTempDataVo::getDataFlag)),
                configureBuilder, data);

        // 生成模板
        log.warn("----------------" + data);
        XWPFTemplate template = XWPFTemplate.compile(tempInputStream, configureBuilder.build()).render(data);
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        template.write(out);
        //关闭模板流
        tempInputStream.close();
        //关闭根据模板生成流
        template.close();
        //关闭返回流
        out.flush();
        out.close();
        return out.toByteArray();
    }

    /**
     * 加载word数据
     *
     * @param dataMap 数据图
     * @param builder 建设者
     * @param data    数据
     */
    private void loadingWordData(Map<String, List<WordTempDataVo>> dataMap, ConfigureBuilder builder, Map<String, Object> data) {
        HackLoopTableRenderPolicy loopRowTableRenderPolicy = new HackLoopTableRenderPolicy();
        TextRenderPolicy textRenderPolicy = new TextRenderPolicy();
        Map<String, List<String>> numberingRenderData = new HashMap<>();
        for (Map.Entry<String, List<WordTempDataVo>> entry : dataMap.entrySet()) {
            List<JSONObject> tableDataObjList = new ArrayList<>();
            boolean isTable = false;
            boolean isList = false;
            for (WordTempDataVo tempData : entry.getValue()) {
                // 对象
                if (OfficeDataEnums.FormDataType.FORM.equals(tempData.getFormDataType())) {
                    // 重新拼接表单数据的key，用于匹配word中的占位符，保存到word数据集中 例如{{1.phone}}，{{2.name}}等
                    for (Map.Entry<String, Object> obj : JSONUtil.parseObj(tempData.getFormData())) {
                        // formDataMapEntry的key是dataFlag，使用dataFlag和表单数据的字段名重新组成word数据
                        String newKey = entry.getKey() + "_" + obj.getKey();
                        data.put(newKey, obj.getValue());
                        builder.bind(newKey, textRenderPolicy);
                    }
                }
                // 表格
                if (OfficeDataEnums.FormDataType.LIST.equals(tempData.getFormDataType())) {
                    isTable = true;
                    tableDataObjList.add(JSONUtil.parseObj(tempData.getFormData()));
                }
                // 列表
                if (OfficeDataEnums.FormDataType.LIST.equals(tempData.getFormDataType())) {
                    isList = true;
                    JSONObject formDataObj = JSONUtil.parseObj(tempData.getFormData());
                    // 重新拼接表单数据的key，用于匹配word中的占位符，保存到word数据集中 例如{{1.phone}}，{{2.name}}等
                    for (Map.Entry<String, Object> formDataObjEntry : formDataObj) {
                        // formDataMapEntry的key是dataFlag，使用dataFlag和表单数据的字段名重新组成word数据
                        String newKey = entry.getKey() + "_" + formDataObjEntry.getKey();
                        //获取当前key的值
                        log.warn(tempData.getFormData() + "-----------" + formDataObjEntry.getKey());
                        String str = String.valueOf(JSONUtil.parseObj(tempData.getFormData()).get(formDataObjEntry.getKey()));
                        //将数据存入列表中
                        if (numberingRenderData.containsKey(newKey)) {
                            numberingRenderData.get(newKey).add(str);
                        } else {
                            List<String> list = new ArrayList<>();
                            list.add(str);
                            numberingRenderData.put(newKey, list);
                        }
                    }
                }
            }
            if (isTable && CollUtil.isNotEmpty(tableDataObjList)) {
                // 将表格唯一标识和行循环插件绑定，需要在模板中表格表头的第一列的列名称前填入{{dataFlag}}，例如{{6_11}}
                builder.bind(entry.getKey(), loopRowTableRenderPolicy);
                // 保存到word数据集中
                data.put(entry.getKey(), tableDataObjList);
            }

            if (isList && CollUtil.isNotEmpty(numberingRenderData)) {
                for (Map.Entry<String, List<String>> formDataObjEntry : numberingRenderData.entrySet()) {
                    // 将表格唯一标识和行循环插件绑定，需要在模板中表格表头的第一列的列名称前填入{{dataFlag}}，例如{{6_11}}
                    // configureBuilder.bind(formDataObjEntry.getKey(), textRenderPolicy);
                    // 保存到word数据集中
                    List<String> renderList = formDataObjEntry.getValue();
                    TextRenderData[] textRenderData = new TextRenderData[renderList.size()];
                    int i = 0;
                    for (String text : renderList) {
                        TextRenderData t = new TextRenderData();
                        t.setText(text);
                        textRenderData[i] = t;
                        i++;
                    }
                    data.put(formDataObjEntry.getKey(), Numberings.of(textRenderData).create());
                }
            }
        }
    }

    /**
     * 封装数据
     *
     * @param params params
     * @return 执行结果
     */
    private List<OfficeDataAllVo> encapsulationData(OfficeMakeDto params) {
        List<OfficeDataAllVo> list = this.officeDataApi.all(new OfficeDataManagementQuery()
                .setBusinessTypeList(params.getBusinessTypeList())).getData();
        for (OfficeDataAllVo data : list) {
            // 基础数据
            if (OfficeDataEnums.FieldType.BASE.equals(data.getFieldType())) {
                log.info("data 数据输出 --> {}", JSONUtil.toJsonStr(data));
                data.setContentData(this.officeDataApi.basePreview(data.getManagementId(), params.getCustomParams()).getData());
            } else {
                // 自定义参数
                JSONObject obj = JSONUtil.createObj();
                // 遍历该分组下所有的字段
                for (OfficeDataFieldsAllVo field : data.getFields()) {
                    obj.set(field.getFieldKey(), this.officeDataCustomApi.customPreview(field.getId(), params.getCustomParams()).getData());
                }
                data.setContentData(obj);
            }
        }
        return list;
    }

    /**
     * 解析word数据
     *
     * @param dataList     数据列表
     * @param wordDataList 数据图
     */
    private void parsingWordData(List<WordTempDataVo> wordDataList, List<OfficeDataAllVo> dataList) {
        if (ObjectUtils.isNotEmpty(dataList)) {
            for (OfficeDataAllVo data : dataList) {
                // 返回值为对象
                if (OfficeDataEnums.ResultType.OBJECT.equals(data.getResultType())) {
                    JSONObject obj = JSONUtil.parseObj(data.getContentData());
                    this.convertWordData(wordDataList, data, obj, OfficeDataEnums.FormDataType.FORM);
                } else if (OfficeDataEnums.ResultType.LIST.equals(data.getResultType())) {
                    if (ObjectUtils.isEmpty(data.getContentData())) {
                        this.convertWordData(wordDataList, data, JSONUtil.createObj(), OfficeDataEnums.FormDataType.LIST);
                    } else {
                        JSONArray jsonArray = JSONUtil.parseArray(data.getContentData());
                        jsonArray.jsonIter().forEach(obj ->
                                this.convertWordData(wordDataList, data, obj, OfficeDataEnums.FormDataType.LIST)
                        );
                    }
                }
            }
        }
    }

    /**
     * 转换word数据
     *
     * @param dataList     数据列表
     * @param data         基本数据
     * @param obj          对象
     * @param formDataType 数据类型
     */
    private void convertWordData(List<WordTempDataVo> dataList, OfficeDataAllVo data, JSONObject obj, OfficeDataEnums.FormDataType formDataType) {
        List<OfficeDataFieldsAllVo> fields = data.getFields();
        WordTempDataVo temp = new WordTempDataVo()
                .setFormDataType(formDataType)
                .setDataFlag(data.getName());
        JSONObject jsonObject = JSONUtil.createObj();
        for (OfficeDataFieldsAllVo field : fields) {
            jsonObject.set(field.getFieldKey(), obj.get(field.getFieldKey()));
        }
        temp.setFormData(jsonObject.toString());
        dataList.add(temp);
    }

    /**
     * 解析excel数据
     *
     * @param excelData excel数据
     * @param dataList  数据列表
     */
    private void parsingExcelData(ExcelTempDataVo excelData, List<OfficeDataAllVo> dataList) {
        for (OfficeDataAllVo data : dataList) {
            if (OfficeDataEnums.ResultType.OBJECT.equals(data.getResultType())) {
                JSONObject obj = JSONUtil.parseObj(data.getContentData());
                this.convertExcelData(excelData, data, obj, OfficeDataEnums.FormDataType.FORM);
            } else if (OfficeDataEnums.ResultType.LIST.equals(data.getResultType())) {
                JSONArray jsonArray = JSONUtil.parseArray(data.getContentData());
                jsonArray.jsonIter().forEach(obj -> this.convertExcelData(excelData, data, obj, OfficeDataEnums.FormDataType.LIST));
            }
        }
    }

    /**
     * 转换excel数据
     *
     * @param excelData    excel数据
     * @param baseData     基本数据
     * @param obj          对象
     * @param formDataType 表单数据类型
     */
    private void convertExcelData(ExcelTempDataVo excelData, OfficeDataAllVo baseData, JSONObject obj, OfficeDataEnums.FormDataType formDataType) {
        for (OfficeDataFieldsAllVo field : baseData.getFields()) {
            if (OfficeDataEnums.FormDataType.FORM.equals(formDataType)) {
                excelData.getTotalData().set(baseData.getName() + "_" + field.getFieldKey(), obj.get(field.getFieldKey()));
            } else if (OfficeDataEnums.FormDataType.LIST.equals(formDataType)) {
                JSONObject json = JSONUtil.createObj();
                json.set(baseData.getName() + "_" + field.getFieldKey(), obj.get(field.getFieldKey()));
                excelData.getListData().add(json);
            }
        }
    }

    /**
     * 复制模板
     *
     * @param temp 临时雇员
     * @return 执行结果
     */
    @Transactional(rollbackFor = Exception.class)
    public R<String> copy(OfficeTemp temp) {
        // 修改模板名称
        temp.setTempName(temp.getTempName() + "(副本)")
                .setId(Tools.Id.uuid());
        OfficeTempRecord record = this.officeTempRecordMapper.selectById(temp.getOfficeTempRecordId());
        if (ObjectUtils.isEmpty(record)) {
            // 创建office模板记录
            String recordId = this.createOfficeTempRecord(temp, null, null);
            temp.setOfficeTempRecordId(recordId);
        } else {
            // 复制模板记录
            record.setOfficeTempId(temp.getId())
                    .setEnable(Boolean.TRUE)
                    .setId(Tools.Id.uuid());
            record.insert();
            temp.setOfficeTempRecordId(record.getId());
        }
        temp.insert();
        return R.ok(temp.getId());
    }

    /**
     * 删除
     *
     * @param id id
     * @return 执行结果
     */
    @Transactional(rollbackFor = Exception.class)
    public R<String> delete(String id) {
        this.removeById(id);
        this.officeTempRecordMapper.delete(new QueryWrapper<OfficeTempRecord>().lambda()
                .eq(OfficeTempRecord::getOfficeTempId, id)
        );
        return R.ok();
    }
}

